﻿namespace MockingBird.Simulator.Framework.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.Serialization;
    using MockingBird.Common.Infrastructure.Extensions;
    using MockingBird.Common.Infrastructure.Helpers;
    using MockingBird.Common.Infrastructure.Logging;
    using MockingBird.Common.Infrastructure.Validation;
    using MockingBird.Simulator.Framework.Exceptions;

    public class HandlerConfigurationManager : IHandlerConfigurationManager
    {
        private HandlerConfiguration configurationData;
        private ILogger logger;
        private ApplicationConfiguration appConfiguration;
        public List<string> ValidationWarnings { get; private set; }
        public List<string> ValidationErrors { get; private set; }
        public string ConfigurationFileName { get; private set; }
        public HandlerConfiguration ConfigurationData
        {
            get
            {
                return configurationData;
            }
            set
            {
                configurationData = value;
            }
        }
        #region "constructor(s)"

        public HandlerConfigurationManager(ILogger logger, ApplicationConfiguration appConfig)
        {
            this.logger = logger;
            this.ValidationWarnings = new List<string>();
            this.ValidationErrors = new List<string>();
            this.appConfiguration = appConfig;

        }

        #endregion

        #region "public methods
        public void InitializeConfigurationData(string configFileName)
        {
            // pass to the overloaded method
            this.InitializeConfigurationData(configFileName, false);
        }

        public void InitializeConfigurationData(string configFileName, bool forceReload)
        {
            logger.Log("HandlerConfigurationManager:InitializeConfigurationData()");
            //now do the sanity checks prior to assigning it to the member variable
            if (String.IsNullOrEmpty(configFileName))
            {
                ThrowException(new HandlerConfigurationException("configurationFileName is null or empty."));
            }
            //Check that the configuration file exists
            if (!File.Exists(configFileName))
            {
                ThrowException(new HandlerConfigurationException("config file could not be found" + configFileName));
            }

            this.ConfigurationFileName = configFileName;

            // if it is explicitly set to forceReload, we oblige, otherwise we load
            // only if there isnt config data available already
            if (forceReload)
            {
                LoadConfigurationDataFromFile();
            }
            else
            {
                if (ConfigurationData == null)
                {
                    LoadConfigurationDataFromFile();
                }

            }

        }

        public void PersistConfiguration(string fileName, HandlerConfiguration configurationData)
        {
            try
            {
                XmlSerializer serializer = new XmlSerializer(typeof(HandlerConfiguration));
                using (XmlTextWriter xtw = new XmlTextWriter(fileName, System.Text.Encoding.UTF8))
                {
                    serializer.Serialize(xtw, configurationData);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        public void Validate()
        {
            //Add all the exceptions to the ValidationErrors and all warnings to the validation Warnings
            //At the end we decide whether or not to throw an exception
            bool isValid = DoStructuralValidation();
            if (!isValid)
            {
                //Get the list of schema validation errors (using the extension method) and throw an exception
                string schemaValErrors = ValidationErrors.Flatten(":");

                ThrowException(new HandlerConfigurationException("Invalid According to Schema. The following errors were found " + schemaValErrors));
            }
            //if it has come so far then it is schematically valid but there may be business errors
            DoBusinessValidation();
            if (ValidationErrors.Count > 0)
            {
                string bizValErrors = ValidationErrors.Flatten(":");
                ThrowException(new HandlerConfigurationException(String.Format("Configuration has business errors. The following errors were found {0}", bizValErrors)));
            }
        }

        public string GetOperationInferenceStrategy()
        {
            return ConfigurationData.ControlSettings.OperationInferenceStrategy.ToString();
        }

        /// <summary>
        /// Returns the map of soap actions to operations.
        /// </summary>
        /// <returns>Map dictionary.</returns>
        public Dictionary<string, string> GetActionMap()
        {
            Dictionary<string, string> actionMap = new Dictionary<string, string>();
            foreach (OperationMapEntryType mapEntry in ConfigurationData.SoapActionOperationMap)
            {
                actionMap.Add(mapEntry.Key, mapEntry.OperationName);
            }

            return actionMap;
        }

        /// <summary>
        /// Returns the map of request root elements to operations.
        /// </summary>
        /// <returns>Request dictionary.</returns>
        public Dictionary<string, string> GetRequestMap()
        {
            Dictionary<string, string> requestMap = new Dictionary<string, string>();
            foreach (OperationMapEntryType mapEntry in ConfigurationData.RequestRootElementOperationMap)
            {
                requestMap.Add(mapEntry.Key, mapEntry.OperationName);
            }
            return requestMap;
        }

        public bool IsOneWay(string operationName)
        {
            return this.Find(operationName).ResponseStrategy == ResponseStrategyType.None;
        }

        public OperationType Find(string operationName)
        {
            return this.ConfigurationData.Operations.Find(op => op.Name.Equals(operationName));
        }

        public OperationType FindFromSoapAction(string soapAction)
        {
            return this.ConfigurationData.Operations.Find(op => (!String.IsNullOrEmpty(op.SoapAction) && op.SoapAction.Equals(soapAction)));
        }

        public ResponseType GetDefaultResponse(OperationType operation)
        {
            return (ResponseType)operation.Item;
        }

        public ResponseType GetXPathResponse(OperationType operation, XmlDocument xDoc)
        {
            bool isFound = false;
            ResponseType locatedResponse = null;

            //Get the collection of XPath responses
            XPathBasedResponsesType xpResponses = (XPathBasedResponsesType)operation.Item;

            List<XPathBasedResponseType> xpResponseList = new List<XPathBasedResponseType>();
            xpResponseList = xpResponses.XPathBasedResponse;
            //Now iteratre through this collection
            foreach (XPathBasedResponseType xpResponse in xpResponseList)
            {
                //Evaluate the XPath expression and result against the xmlDocument (using the extension method).
                //If it matches then return
                isFound = xDoc.IsSuccessfulEvaluation(xpResponse.Expression, xpResponse.Result);
                if (isFound)
                {
                    //The data we want is in the "Response" part of the xpResponse object
                    locatedResponse = xpResponse.Response;
                    break;
                }

            }

            //Now return the locatedResponse. it could be null if no XPath evaluation was successful
            return locatedResponse;
        }
        #endregion



        #region "private validation methods"

        private bool DoStructuralValidation()
        {
            //string xsdLocation = ((NameValueCollection)ConfigurationManager.GetSection("MockingBird.Configuration/Global")).Get("ConfigSchemaFilesLocation");
            string xsdLocation = appConfiguration.ConfigSchemaFilesLocation;

            xsdLocation = Path.Combine(xsdLocation, "HandlerConfiguration.xsd");
            bool isValid;

            XDocument configDoc = XmlHelper.SerializeToXDocument(ConfigurationData, typeof(HandlerConfiguration), null);

            isValid = SchemaValidator.ValidateXml(configDoc, xsdLocation);
            this.ValidationErrors = SchemaValidator.ValidationErrors;

            return isValid;


        }

        private void DoBusinessValidation()
        {
            if (String.IsNullOrEmpty(ConfigurationData.Wsdl))
                ValidationWarnings.Add("WSDL is null or empty. Could cause issues for any WSDL dependent functionality");

            //Now if the operations is not null, we can cycle through the list
            if (ConfigurationData.Operations != null)
            {
                foreach (OperationType operation in ConfigurationData.Operations)
                {
                    ValidateOperation(operation);
                }
            }
        }

        private void ValidateOperation(OperationType operation)
        {
            switch (operation.ResponseStrategy)
            {
                case ResponseStrategyType.Default:
                    ValidateDefaultResponseOperation(operation);
                    break;
                case ResponseStrategyType.XPath:
                    ValidateXPathReponsesOperation(operation);
                    break;
                case ResponseStrategyType.Xslt:
                    ValidateXsltReponseOperation(operation);
                    break;
                case ResponseStrategyType.None:
                    ValidateOneWayOperation(operation);
                    break;
                default:
                    break;
            }

        }

        private void ValidateOneWayOperation(OperationType operation)
        {
            return;
        }
        private void ValidateDefaultResponseOperation(OperationType operation)
        {
            ResponseType response = GetDefaultResponse(operation);
            ValidateResponse(operation.Name, response, ResponseStrategyType.Default.ToString());
        }
        private void ValidateXPathReponsesOperation(OperationType operation)
        {
            //Get the XPath responses and then cycle through them and call ValidateResponse for each
            XPathBasedResponsesType xpResponses = (XPathBasedResponsesType)operation.Item;
            List<XPathBasedResponseType> xpResponseList = new List<XPathBasedResponseType>();
            xpResponseList = xpResponses.XPathBasedResponse;
            //Now iteratre through this collection
            foreach (XPathBasedResponseType xpResponse in xpResponseList)
            {
                ValidateResponse(operation.Name, xpResponse.Response, ResponseStrategyType.XPath.ToString());
            }
        }
        private void ValidateXsltReponseOperation(OperationType operation)
        {
            ResponseType response = GetDefaultResponse(operation);
            ValidateResponse(operation.Name, response, ResponseStrategyType.Xslt.ToString());
        }
        private void ValidateResponse(string operationName, ResponseType response, string responseKind)
        {
            //check the combination of responsebehavior and error type
            if ((response.ResponseBehaviour == ResponseBehaviourType.Error) &&
                (response.ErrorType == ErrorType.None))
            {
                ValidationErrors.Add(String.Format("Operation Name {0} with Response Strategy {1} has ResponseBehavior set to Error but ErrorType is None", operationName, responseKind));
            }
            //check for the existence of the file
            if (!File.Exists(response.FileName))
            {
                ValidationWarnings.Add(String.Format("Operation Name {0} with Response Strategy {1} has a FileName that does not exist", operationName, responseKind));

            }

        }


        #endregion

        #region "private methods"
        private void LoadConfigurationDataFromFile()
        {
            logger.Log("Attempting to LoadConfigurationDataFromFile named : " + ConfigurationFileName);
            //Use the XmlHelper to serialize the configuration file into the required object
            //if the file is invalid xml it will automatically throw errors

            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(ConfigurationFileName);

            try
            {
                ConfigurationData = (HandlerConfiguration)XmlHelper.Deserialize(xDoc, typeof(HandlerConfiguration), "urn:mockingbird.engine.business.configuration:handlerconfiguration");
            }
            catch (InvalidOperationException iox)
            {
                ThrowException(new HandlerConfigurationException("Deserialization into configuration failed. Inner exception message is" + iox.Message));
            }
            catch (XmlException xe)
            {
                ThrowException(new HandlerConfigurationException("Deserialization into configuration failed. Inner exception message is" + xe.Message));
            }
        }
        private void ThrowException(Exception e)
        {
            logger.LogError(e.Message, e);
            throw e;

        }
        #endregion
    }
}
